// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;

using Microsoft.TeamFoundation.Migration.Toolkit;
using Microsoft.TeamFoundation.Migration.Toolkit.Wit;

namespace Microsoft.TeamFoundation.Migration.Toolkit.Linking
{
    /// <summary>
    /// Link analyzer class is used to detect unmatched links between two collections.
    /// </summary>
    public class LinkAnalyzer
    {
        private Pair<ReadOnlyCollection<ILink>> m_forSyncing;             // Links that can be migrated to the opposite side
        private Pair<ReadOnlyCollection<ILink>> m_forDeferral;            // Links that cannot be migrated because target is missing
        private Pair<ReadOnlyCollection<ILink>> m_deferred;               // Deferred links on both sides

        /// <summary>
        /// Returns unmatched links from both sides that can be migrated.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        public Pair<ReadOnlyCollection<ILink>> ForSyncing { get { return m_forSyncing; } }

        /// <summary>
        /// Returns unmatched links from both sides that cannot be migrated.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        public Pair<ReadOnlyCollection<ILink>> ForDeferral { get { return m_forDeferral; } }

        /// <summary>
        /// Returns deferred links on both sides.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        public Pair<ReadOnlyCollection<ILink>> Deferred { get { return m_deferred; } }

        /// <summary>
        /// Tells whether both collections are 100% in sync.
        /// </summary>
        public bool InSync
        {
            get
            {
                return m_forSyncing.Left.Count == 0 && m_forSyncing.Right.Count == 0 &&
                    m_forDeferral.Left.Count == 0 && m_forDeferral.Right.Count == 0;
            }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="engine">Linking engine</param>
        /// <param name="leftSystem">Left system</param>
        /// <param name="sources">Link sources</param>
        public LinkAnalyzer(
            ILinkEngine engine,
            SystemType leftSystem,
            Pair<ILinkContainer> sources)
        {
            if (engine == null)
            {
                throw new ArgumentNullException("engine");
            }
            if (sources == null)
            {
                throw new ArgumentNullException("sources");
            }

            Pair<List<ILink>> forSyncing = new Pair<List<ILink>>(new List<ILink>(), new List<ILink>());
            Pair<List<ILink>> forDeferral = new Pair<List<ILink>>(new List<ILink>(), new List<ILink>());

            SystemType rightSystem = leftSystem == SystemType.Tfs? SystemType.Other : SystemType.Tfs;
            List<ILink> leftLinks, leftDeferred;
            List<ILink> rightLinks, rightDeferred;

            LoadLinks(engine, leftSystem, sources.Left, out leftLinks, out leftDeferred);
            LoadLinks(engine, rightSystem, sources.Right, out rightLinks, out rightDeferred);

            Pair<List<ILink>> links = new Pair<List<ILink>>(leftLinks, rightLinks);
            Pair<List<ILink>> deferred = new Pair<List<ILink>>(leftDeferred, rightDeferred);

            for (int i = 0; i < links.Left.Count; i++)
            {
                ILink link = links.Left[i];
                string targetUri = engine.FindReflection(leftSystem, link.TargetUri);

                if (string.IsNullOrEmpty(targetUri))
                {
                    // Link's target does not exist on the opposite side
                    if (deferred.Left.BinarySearch(link, LinkComparer.Current) < 0)
                    {
                        forDeferral.Left.Add(link);
                    }
                }
                else
                {
                    // Link's target does exist on the opposite side.
                    ILink reflection = link.Redirect(targetUri);
                    int pos = links.Right.BinarySearch(reflection, LinkComparer.Current);

                    if (pos >= 0)
                    {
                        // The item exists on both sides, so all deferred links must be removed
                        if (deferred.Left.BinarySearch(link, LinkComparer.Current) >= 0)
                        {
                            engine.RemoveDeferred(leftSystem, link);
                        }
                        if (deferred.Right.BinarySearch(reflection, LinkComparer.Current) >= 0)
                        {
                            engine.RemoveDeferred(rightSystem, reflection);
                        }

                        // Remove the item from the right
                        links.Right.RemoveAt(pos);
                    }
                    else
                    {
                        forSyncing.Left.Add(reflection);
                    }
                }
            }

            // Process unmatched right items
            for (int i = 0; i < links.Right.Count; i++)
            {
                ILink link = links.Right[i];
                string targetUri = engine.FindReflection(rightSystem, link.TargetUri);

                if (string.IsNullOrEmpty(targetUri))
                {
                    // Link's target does not exist on the opposite side.
                    if (deferred.Right.BinarySearch(link, LinkComparer.Current) < 0)
                    {
                        forDeferral.Right.Add(link);
                    }
                }
                else
                {
                    ILink reflection = link.Redirect(targetUri);

                    // We have already removed all matched items from both sides. If something
                    // still matches, link comparison is implemented incorrectly.
                    Debug.Assert(links.Left.BinarySearch(reflection, LinkComparer.Current) < 0, "Link still exists on the left side!");
                    forSyncing.Right.Add(reflection);
                }
            }

            m_forSyncing = new Pair<ReadOnlyCollection<ILink>>(
                forSyncing.Left.AsReadOnly(),
                forSyncing.Right.AsReadOnly());
            m_forDeferral = new Pair<ReadOnlyCollection<ILink>>(
                forDeferral.Left.AsReadOnly(),
                forDeferral.Right.AsReadOnly());
            m_deferred = new Pair<ReadOnlyCollection<ILink>>(
                deferred.Left.AsReadOnly(),
                deferred.Right.AsReadOnly());
        }

        /// <summary>
        /// Extracts active and deferred links from the source deleting obsolete deferred links.
        /// </summary>
        /// <param name="engine">Linking engine</param>
        /// <param name="system">System type</param>
        /// <param name="source">Source of links</param>
        /// <param name="active">List of active links</param>
        /// <param name="deferred">List of deferred links</param>
        private static void LoadLinks(
            ILinkEngine engine,
            SystemType system,
            ILinkContainer source,
            out List<ILink> active,
            out List<ILink> deferred)
        {
            if (source != null)
            {
                active = new List<ILink>(engine.GetLinks(source));
                active.Sort(LinkComparer.Current);

                deferred = new List<ILink>();
                List<ILink> tempDeferred = new List<ILink>(engine.GetDeferredLinks(system, source.Uri));

                // Remove obsolete deferred links
                for (int i = 0; i < tempDeferred.Count; i++)
                {
                    ILink link = tempDeferred[i];

                    if (active.BinarySearch(link, LinkComparer.Current) < 0)
                    {
                        // The link is obsolete; delete it.
                        engine.RemoveDeferred(system, link);
                    }
                    else
                    {
                        deferred.Add(link);
                    }
                }
                deferred.Sort(LinkComparer.Current);
            }
            else
            {
                active = new List<ILink>();
                deferred = new List<ILink>();
            }
        }
    }
}
